﻿//-----------------------------------------------------------------------
// <copyright>
//     Copyright (c) Artur Mustafin. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Expressions.Compiler
{
    using System.Collections.Generic;

    using Internal;

    /// <summary>
    /// A nonterminal
    /// </summary>
    public sealed class Nonterminal<T> : Entity<T>
    {
        /// <summary>
        /// A string name
        /// </summary>
        private readonly string _name;

        /// <summary>
        /// 
        /// </summary>
        private readonly Rules<T> _rules;

        /// <summary>
        /// Gets the <see cref="Expressions.Compiler.Rule"/> with the specified definition.
        /// </summary>
        /// <value></value>
        internal Rule<T> this[Rules<T>.Definition definition]
        {
            get
            {
                return _rules[definition];
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Nonterminal"/> class.
        /// </summary>
        /// <param name="name">The input name</param>
        private Nonterminal(string name)
        {
            _name = name;
            _rules = new Rules<T>(this);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.String"/> to <see cref="Expressions.Compiler.Nonterminal"/>.
        /// </summary>
        /// <param name="name">The input name</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator Nonterminal<T>(string name)
        {
            return GetNonterminal(name);
        }

        /// <summary>
        /// Gets the <see cref="Expressions.Compiler.Tokens"/> with the specified optional.
        /// </summary>
        /// <param name="optional">An optional value</param>
        /// <returns>A Stream</returns>
        public Tokens<T> this[bool optional]
        {
            get
            {
                return GetTokens(GetNonterminal(_name, optional));
            }
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="Expressions.Compiler.Terminal"/> to <see cref="Expressions.Compiler.Tokens"/>.
        /// </summary>
        /// <param name="ch">The ch.</param>
        /// <returns>The result of the conversion.</returns>
        internal static Tokens<T> GetTokens(Reference<T> value)
        {
            Tokens<T> stream = new Tokens<T>();
            if (value != null)
            {
                stream.Add(value, value.Optional);
            }
            return stream;
        }

        private static readonly Dictionary<string, Nonterminal<T>> _nonterminals;

        static Nonterminal()
        {
            _nonterminals = new Dictionary<string, Nonterminal<T>>();
        }

        private static Reference<T> GetNonterminal(string name, bool optional)
        {
            return new Reference<T>(_nonterminals[name], optional);
        }

        private static Nonterminal<T> GetNonterminal(string name)
        {
            if (!_nonterminals.ContainsKey(name))
            {
                _nonterminals.Add(name, new Nonterminal<T>(name));
            }
            return _nonterminals[name];
        }

        private HashSet<Nonterminal<T>> _nodes = new HashSet<Nonterminal<T>>();

        /// Initializes the instance.
        /// </summary>
        public void Initialize()
        {
            _nodes.Clear();
            InitializeNonterminal(this);
        }

        /// <summary>
        /// Validates this instance.
        /// </summary>
        public void Validate()
        {
            _nodes.Clear();
            ValidateNonterminal(this);
        }

        /// <summary>
        /// Validates the nonterminal.
        /// </summary>
        internal void ValidateNonterminal(Nonterminal<T> parent)
        {
            foreach (Rule<T> rule in _rules.Definitions)
            {
                if (parent.Visit(this))
                {
                    rule.Validate(parent);
                    parent.Unvisit(this);
                }
            }
        }

        /// <summary>
        /// Initializes the nonterminal using the specified parent hash set.
        /// </summary>
        /// <param name="parent">The parent hash set.</param>
        internal void InitializeNonterminal(Nonterminal<T> parent)
        {
            foreach (Rule<T> rule in _rules.Definitions)
            {
                if (parent.Visit(this))
                {
                    rule.Initialize(parent);
                    parent.Unvisit(this);
                }
            }
        }

        /// <summary>
        /// Builds up the lookup table for all rules in the collection.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="optional">if set to <c>true</c> [optional].</param>
        /// <returns>
        /// Returns lookup table for this nonterminal.
        /// </returns>
        internal IEnumerable<Terminal<T>> First(Nonterminal<T> parent, bool optional)
        {
            HashSet<Terminal<T>> terminals = new HashSet<Terminal<T>>();
            foreach (Rule<T> rule in _rules.Definitions)
            {
                foreach (Terminal<T> terminal in rule.First)
                {
                    terminals.Add(terminal[optional]);
                }
            }
            return terminals;
        }
#if AMIBIGOUS
        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">The nonterminal a.</param>
        /// <param name="b">The nonterminal b.</param>
        /// <returns>The result of the operator.</returns>
        public static Tokens operator +(Nonterminal a, Nonterminal b)
        {
            Tokens stream1 = a;
            Tokens stream2 = b;
            return stream1 + stream2;
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">The terminal a.</param>
        /// <param name="b">The nonterminal b.</param>
        /// <returns>The result of the operator.</returns>
        public static Tokens operator +(Terminal a, Nonterminal b)
        {
            Tokens stream1 = a;
            Tokens stream2 = b;
            return stream1 + stream2;
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">The nonterminal a.</param>
        /// <param name="b">The terminal b.</param>
        /// <returns>The result of the operator.</returns>
        public static Tokens operator +(Nonterminal a, Terminal b)
        {
            Tokens stream1 = a;
            Tokens stream2 = b;
            return stream1 + stream2;
        }
#endif

        /// <summary>
        /// Parses the specified text.
        /// </summary>
        /// <param name="parser">The input text</param>
        /// <returns>A boolean value</returns>
        public override bool Parse(Parser<T> parser)
        {
            if (object.Equals(parser.Current.Value, Terminal<T>.Default))
            {
                return false;
            }
            parser.Push();
            Position<T> start = parser.Current;
            foreach (Rule<T> rule in _rules)
            {
#if RECURSION

                if (rule.Parse(parser))
                {
                    Stream end = parser.Current;
                    parser.Parse(new Context(this, Name, start, end));
                    return true;
                }
#else
                foreach (Terminal<T> terminal in rule.First)
                {
                    if (terminal.Optional || parser.Read(terminal))
                    {
                        if (rule.Parse(parser))
                        {
                            Position<T> end = parser.Current;

#if TRACE
                            parser.Parse(this, Name, start, end);
#else
                            parser.Parse(this, start, end);
#endif
                            return true;
                        }
                    }
                }
#endif
            }
            parser.Pop();
            return false;
        }

        public Rules<T> Definition
        {
            set
            {
                _rules.Clear();
                _rules.Set(value.Definitions);
            }
        }

#if TRACE
        internal override string Name
        {
            get
            {
                return _name;
            }
        }

        public override string ToString()
        {
            return _name;
        }
#endif

        internal bool Visit(Nonterminal<T> nonterminal)
        {
            if (!_nodes.Contains(nonterminal))
            {
                _nodes.Add(nonterminal);
                return true;
            }
            return false;
        }
        internal bool Visited(Nonterminal<T> nonterminal)
        {
            return _nodes.Contains(nonterminal);
        }
        internal void Unvisit(Nonterminal<T> nonterminal)
        {
            _nodes.Remove(nonterminal);
        }
    }

    /// <summary>
    /// A nonterminal
    /// </summary>
    public sealed class Nonterminal : Entity
    {
        /// <summary>
        /// A string name
        /// </summary>
        private readonly string _name;

        /// <summary>
        /// 
        /// </summary>
        private readonly Rules _rules;

        /// <summary>
        /// Gets the <see cref="Expressions.Compiler.Rule"/> with the specified definition.
        /// </summary>
        /// <value></value>
        internal Rule this[Rules.Definition definition]
        {
            get
            {
                return _rules[definition];
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Nonterminal"/> class.
        /// </summary>
        /// <param name="name">The input name</param>
        private Nonterminal(string name)
        {
            _name = name;
            _rules = new Rules(this);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.String"/> to <see cref="Expressions.Compiler.Nonterminal"/>.
        /// </summary>
        /// <param name="name">The input name</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator Nonterminal(string name)
        {
            return GetNonterminal(name);
        }

        /// <summary>
        /// Gets the <see cref="Expressions.Compiler.Tokens"/> with the specified optional.
        /// </summary>
        /// <param name="optional">An optional value</param>
        /// <returns>A Stream</returns>
        public Tokens this[bool optional]
        {
            get
            {
                return GetTokens(GetNonterminal(_name, optional));
            }
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="Expressions.Compiler.Terminal"/> to <see cref="Expressions.Compiler.Tokens"/>.
        /// </summary>
        /// <param name="ch">The ch.</param>
        /// <returns>The result of the conversion.</returns>
        internal static Tokens GetTokens(Reference value)
        {
            Tokens stream = new Tokens();
            if (value != null)
            {
                stream.Add(value, value.Optional);
            }
            return stream;
        }

        private static readonly Dictionary<string, Nonterminal> _nonterminals;

        static Nonterminal()
        {
            _nonterminals = new Dictionary<string, Nonterminal>();
        }

        private static Reference GetNonterminal(string name, bool optional)
        {
            return new Reference(_nonterminals[name], optional);
        }

        private static Nonterminal GetNonterminal(string name)
        {
            if (!_nonterminals.ContainsKey(name))
            {
                _nonterminals.Add(name, new Nonterminal(name));
            }
            return _nonterminals[name];
        }

        private HashSet<Nonterminal> _nodes = new HashSet<Nonterminal>();

        /// <summary>
        /// Initializes the instance.
        /// </summary>
        public void Initialize()
        {
            _nodes.Clear();
            InitializeNonterminal(this);
        }

        /// <summary>
        /// Validates this instance.
        /// </summary>
        public void Validate()
        {
            _nodes.Clear();
            ValidateNonterminal(this);
        }

        /// <summary>
        /// Validates the nonterminal.
        /// </summary>
        internal void ValidateNonterminal(Nonterminal parent)
        {
            foreach (Rule rule in _rules.Definitions)
            {
                if (_nodes.Add(this))
                {
                    rule.Validate(parent);
                    _nodes.Remove(this);
                }
            }
        }

        /// <summary>
        /// Initializes the nonterminal using the specified parent hash set.
        /// </summary>
        /// <param name="parent">The parent hash set.</param>
        internal void InitializeNonterminal(Nonterminal parent)
        {
            foreach (Rule rule in _rules.Definitions)
            {
                if (_nodes.Add(this))
                {
                    rule.Initialize(parent);
                    _nodes.Remove(this);
                }
            }
        }

        /// <summary>
        /// Builds up the lookup table for all rules in the collection.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <returns>Returns lookup table for this nonterminal.</returns>
        internal IEnumerable<Terminal> First(Nonterminal parent)
        {
            HashSet<Terminal> terminals = new HashSet<Terminal>();
            foreach (Rule rule in _rules.Definitions)
            {
                //rule.InitializeInternal();
                foreach (Terminal terminal in rule.First)
                {
                    terminals.Add(terminal);
                }
            }
            return terminals;
        }
#if AMIBIGOUS
        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">The nonterminal a.</param>
        /// <param name="b">The nonterminal b.</param>
        /// <returns>The result of the operator.</returns>
        public static Tokens operator +(Nonterminal a, Nonterminal b)
        {
            Tokens stream1 = a;
            Tokens stream2 = b;
            return stream1 + stream2;
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">The terminal a.</param>
        /// <param name="b">The nonterminal b.</param>
        /// <returns>The result of the operator.</returns>
        public static Tokens operator +(Terminal a, Nonterminal b)
        {
            Tokens stream1 = a;
            Tokens stream2 = b;
            return stream1 + stream2;
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">The nonterminal a.</param>
        /// <param name="b">The terminal b.</param>
        /// <returns>The result of the operator.</returns>
        public static Tokens operator +(Nonterminal a, Terminal b)
        {
            Tokens stream1 = a;
            Tokens stream2 = b;
            return stream1 + stream2;
        }
#endif

        /// <summary>
        /// Parses the specified text.
        /// </summary>
        /// <param name="parser">The input text</param>
        /// <returns>A boolean value</returns>
        public override bool Parse(Parser parser)
        {
            if (parser.Current.Char == Terminal.Null)
            {
                return false;
            }
            parser.Push();
            Position start = parser.Current;
            foreach (Rule rule in _rules)
            {
#if RECURSION

                if (rule.Parse(parser))
                {
                    Stream end = parser.Current;
                    parser.Parse(new Context(this, Name, start, end));
                    return true;
                }
#else

#if TRACE
                foreach (Terminal terminal in rule.First)
                {
                    if (terminal.Optional || parser.Read(terminal))
                    {
                        if (rule.Parse(parser))
                        {
                            Position end = parser.Current;
                            parser.Parse(this, Name, start, end);
                            return true;
                        }
                    }
                }
#else
                foreach (Terminal terminal in rule.First)
                {
                    if (terminal.Optional || parser.Read(terminal))
                    {
                        if (rule.Parse(parser))
                        {
                            Position end = parser.Current;
                            parser.Parse(this, start, end);
                            return true;
                        }
                    }
                }
#endif

#endif
            }
            parser.Pop();
            return false;
        }

        public Rules Definition
        {
            set
            {
                _rules.Clear();
                _rules.Set(value.Definitions);
            }
        }

#if TRACE
        internal override string Name
        {
            get
            {
                return _name;
            }
        }

        public override string ToString()
        {
            return _name;
        }
#endif

        internal bool Visit(Nonterminal nonterminal)
        {
            return _nodes.Contains(nonterminal);
        }
    }
}
